import cv2 as cv
import numpy as np

RAD_TO_DEGREE = 180 / np.pi


def filter_by_degree(theta):
    degree = theta * RAD_TO_DEGREE
    threshold = 5
    desire_angles = [0.0, 90.0, 180.0]
    for angle in desire_angles:
        if abs(angle - degree) < threshold:
            return True
    return False


def is_cv2():
    # if we are using OpenCV 2, then our cv2.__version__ will start
    # with '2.'
    return check_opencv_version("2.")


def check_opencv_version(major, lib=None):
    # if the supplied library is None, import OpenCV
    if lib is None:
        import cv2 as lib

    # return whether or not the current OpenCV version matches the
    # major version number
    return lib.__version__.startswith(major)


def is_similar_line(lines, line):
    eval_rho, eval_theta = line
    for rho, theta in lines:
        if abs(theta - eval_theta) < 0.1 and abs(rho - eval_rho) < 20.0:
            return True

    return False


def cluster_lines(lines):
    groups = []
    for i in range(len(lines)):
        for rho, theta in lines[i]:
            if not filter_by_degree(theta):
                continue
            new_group = True
            if len(groups) == 0:
                groups.append([(rho, theta)])
                continue
            for line_group in groups:
                is_line_in_group = is_similar_line(line_group, (rho, theta))
                if is_line_in_group:
                    line_group.append((rho, theta))
                    new_group = False
                    continue

            if new_group:
                groups.append([(rho, theta)])

    # remove group with only one line
    filtered_group = []
    for group in groups:
        if len(group) > 1:
            filtered_group.append(group)
    return filtered_group


def average_line_groups(line_groups):
    averaged_lines = []
    for line_group in line_groups:
        sum_rho = 0.0
        sum_theta = 0.0
        size = len(line_group)
        for rho, theta in line_group:
            sum_rho = sum_rho + rho
            sum_theta = sum_theta + theta
        averaged_lines.append((sum_rho/size, sum_theta/size))
    return averaged_lines


def resize(image, width: int = None, height: int = None):
    if width is None and height is None:
        raise AssertionError("No argument passed")

    h, w = image.shape[:2]
    if width and height:
        return cv.resize(image, (width, height))

    if width:
        dh = int(h * (width / w))
        return cv.resize(image, (width, dh))

    if height:
        dw = int(w * (height / h))
        return cv.resize(image, (dw, height))


def intersect_point(line1, line2):
    rho1, theta1 = line1
    rho2, theta2 = line2
    o1, p1 = transform_line(rho1, theta1)
    o2, p2 = transform_line(rho2, theta2)

    xx = o2[0] - o1[0]
    xy = o2[1] - o1[1]
    d1x = p1[0] - o1[0]
    d1y = p1[1] - o1[1]
    d2x = p2[0] - o2[0]
    d2y = p2[1] - o2[1]

    cross = d1x * d2y - d1y * d2x
    if abs(cross) < 1e-8:
        return -1, -1

    t1 = (xx * d2y - xy * d2x) / cross
    return o1[0] + d1x * t1, o1[1] + d1y * t1


def detect(image):
    height, width = image.shape[:2]
    print("image: ({}, {})".format(width, height))
    resized = resize(image, width=1200)
    r_height, r_width = resized.shape[:2]
    print("resized image: ({}, {})".format(r_width, r_height))

    ratio = image.shape[0] / float(resized.shape[0])

    # convert the resized image to grayscale, blur it slightly,
    # and threshold it
    gray = cv.cvtColor(resized, cv.COLOR_BGR2GRAY)
    blurred = cv.GaussianBlur(gray, (9, 9), 0)
    thresh = cv.adaptiveThreshold(blurred, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 19, 9)
    canny = cv.Canny(thresh, 30, 70)

    lines = cv.HoughLines(canny, 1, np.pi / 180, 85)
    line_groups = cluster_lines(lines)
    print("# of line groups: {}".format(len(line_groups)))
    for line_group in line_groups:
        print(line_group)

    averaged_lines = average_line_groups(line_groups)

    intersection_points = []
    line_group_size = len(averaged_lines)
    for i in range(line_group_size):
        for j in range(line_group_size):
            if i == j:
                continue
            x, y = intersect_point(averaged_lines[i], averaged_lines[j])
            if (x < 0 or y < 0) or (x > r_width or y > r_height):
                continue
            intersection_points.append((x, y))
            print("intersected point: ({}, {})".format(x, y))

    p1, p2 = bounding_box(intersection_points)
    print("bounding box: ({}, {}), ({}, {})".format(p1[0], p1[1], p2[0], p2[1]))
    # cv.rectangle(resized, p1, p2, (0, 255, 0), 2)
    # cv.imshow('resized', resized)
    # cv.waitKey(0)

    # for rho, theta in averaged_lines:
    #     p1, p2 = transform_line(rho, theta)
    #     print("line's rho: {}, theta: {}, degree: {}".format(rho, theta, theta * RAD_TO_DEGREE))
    #     cv.line(resized, p1, p2, (0, 0, 255), 2)
    #     cv.imshow('resized', resized)
    #     cv.waitKey(0)

    # crop the bounding box area from original image
    o_p1 = (int(p1[0] * ratio), int(p1[1] * ratio))
    o_p2 = (int(p2[0] * ratio), int(p2[1] * ratio))
    print("original bounding box: ({}, {}), ({}, {})".format(o_p1[0], o_p1[1], o_p2[0], o_p2[1]))

    area_of_interest = image[o_p1[1]:o_p2[1], o_p1[0]:o_p2[0]]
    # cv.imshow("area_of_interest", area_of_interest)
    # cv.waitKey(0)
    return area_of_interest


def transform_line(rho, theta):
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * (a))
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * (a))
    return (x1, y1), (x2, y2)


def bounding_box(points):
    min_x = 10e6
    min_y = 10e6
    max_x = -1
    max_y = -1
    for x, y in points:
        if x < min_x:
            min_x = x
        if x > max_x:
            max_x = x
        if y < min_y:
            min_y = y
        if y > max_y:
            max_y = y
    return (int(min_x), int(min_y)), (int(max_x), int(max_y))

